import os
import pandas as pd
from dash import (
    dcc,
    html,
    callback,
    Output,
    Input,
    State,
    register_page,
    get_app,
)
import feffery_antd_components as fac
from xq_factor import add_api, remove_api, get_api,set_option,get_option,Config
from xq_factor.factor import LeafFactor, ObjectedLeafFactor, SingleLeafFactor
from xq_factor.operator import FILLNA
from xqdata.constant import Frequency,SecuType,Exchange
from gypb.apps.performance_analysis import HoldingAnalyzer
from gypb.db import get_ddbapi, get_rqapi
from gypb.component import DFTable
from flask_caching import Cache

ddbapi = get_ddbapi()
add_api(ddbapi)
rqapi = get_rqapi()
add_api(rqapi,"rq")

app = get_app()
CACHE_CONFIG = {
    # try 'FileSystemCache' if you don't want to setup redis
    'CACHE_TYPE': 'redis',
    'CACHE_REDIS_URL': os.environ.get('REDIS_URL', 'redis://localhost:6379')
}
cache = Cache()
cache.init_app(app.server, config=CACHE_CONFIG)

# 注册页面
register_page(__name__, name="持仓分析", order=9)

def layout():
    return html.Div(
        [
            fac.AntdSelect(
                id="perfanalysis-select-product",
                placeholder="请选择产品",
                options=[],
                autoSpin=True,
                debounceWait=800,
                style={"width": "600px"},
            ),
            fac.AntdSelect(
                id="perfanalysis-select-benchmark",
                placeholder="请选择对标基准",
                options=[
                    {"value": "上证指数", "label": "上证指数"},
                    {"value": "沪深300", "label": "沪深300"},
                    {"value": "中证500", "label": "中证500"},
                    {"value": "中证1000", "label": "中证1000"},
                ],
                value="沪深300",
                autoSpin=True,
                debounceWait=800,
                style={"width": "600px"},
            ),
            fac.AntdDateRangePicker(
                id="perfanalysis-date-range-picker",
                placeholder=["分析起始日期", "分析结束日期"],
            ),
            fac.AntdButton("计算归因", id="button-performance-analysis", type="primary"),
            fac.AntdTitle("持仓明细", level=2),
            html.Div(id="holding_detail"),
            fac.AntdTitle("持股数量", level=2),
            dcc.Graph(id="holding_num_fig"),
            fac.AntdTitle("行业集中度", level=2),
            fac.AntdSelect(
                id="industry_figure_mode",
                placeholder="时序/截面",
                options=[
                    {"value": "截面", "label": "截面"},
                    {"value": "时序", "label": "时序"},
                ],
                value="截面",
                autoSpin=True,
            ),
            fac.AntdSelect(
                id="industry_figure_param",
                placeholder="请选择时间/风险因子",
                autoSpin=True,
                style={"width": "150px"},
            ),
            dcc.Graph(id="industry_fig"),
            fac.AntdTitle("风格暴露", level=2),
            fac.AntdSelect(
                id="riskfactor_figure_mode",
                placeholder="时序/截面",
                options=[
                    {"value": "截面", "label": "截面"},
                    {"value": "时序", "label": "时序"},
                ],
                value="截面",
                autoSpin=True,
            ),
            fac.AntdSelect(
                id="riskfactor_figure_param",
                placeholder="请选择时间/风险因子",
                autoSpin=True,
                style={"width": "150px"},
            ),
            dcc.Graph(id="riskfactor_fig"),

            dcc.Store(id="calculated_signal"),
        ]
    )

@cache.memoize()
def get_analyze_result(prodcode: str, benchmark:str,start, end):
    # print("开始计算",pd.Timestamp.now())
    # 处理参数
    A_stock_list = rqapi.get_secuinfo(SecuType.STOCK)
    HK_stock_list = rqapi.get_secuinfo(SecuType.STOCK,exchange=Exchange.HK)
    universe = A_stock_list.index.to_list() + HK_stock_list.index.to_list()
    # universe = A_stock_list.index.to_list()
    prod_code, name = prodcode.split("-")
    # prodcode_account_info = ddbapi.get_info("prodcode_account").query(f"prod_code=={prod_code}")
    prodcode_account_info = ddbapi.get_info("prodcode_account")
    prodcode_account_info = prodcode_account_info[prodcode_account_info.prod_code == prod_code]
    # print(prodcode_account_info)
    benchmark_map = {
        "上证指数": "000001.SSE",
        "沪深300": "000300.SSE",
        "中证500": "000905.SSE",
        "中证1000": "000852.SSE",
    }
    benchmark = benchmark_map[benchmark]
    # 定义分析配置项
    config = Config()
    config.set_option("universe", universe)
    config.set_option("start_time", start)
    config.set_option("end_time", end)
    config.set_option("frequency", Frequency.DAILY)
    # 定义因子
    market_value = sum(
        [
            FILLNA(ObjectedLeafFactor("market_value", account))
            for account in prodcode_account_info["account"].to_list()
        ]
    )
    benchmark_weight = ObjectedLeafFactor("constituent_weight",benchmark,)
    citics_2019 = LeafFactor("citics_2019_l1_name",api="rq")
    risk_factors = [
        "beta",
        "momentum",
        "size",
        "book_to_price",
        "non_linear_size",
        "earnings_yield",
        "residual_volatility",
        "growth",
        "leverage",
        "liquidity",
    ]
    riskfactors = {f:LeafFactor(f) for f in risk_factors}
    result = HoldingAnalyzer(name,benchmark_weight,citics_2019,riskfactors,config).process(market_value)
    # print("计算完成",pd.Timestamp.now())
    return result

@callback(
    Output("calculated_signal", "data"),
    Input("button-performance-analysis", "nClicks"),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def prods_holding_analysis(nClicks, prodcode: str, benchmark:str,date_range):
    if nClicks:
        start, end = pd.to_datetime(date_range)
        get_analyze_result(prodcode, benchmark,start, end)
        return nClicks

@callback(
    Output('holding_detail', 'children'),
    Input('calculated_signal', 'data'),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_holding_detail(signal,prodcode: str, benchmark:str,date_range):
    rqapi = get_rqapi()
    A_stock_list = rqapi.get_secuinfo(SecuType.STOCK)
    HK_stock_list = rqapi.get_secuinfo(SecuType.STOCK,exchange=Exchange.HK)
    stock_name = pd.concat([A_stock_list["symbol"],HK_stock_list["symbol"]])
    # stock_name = A_stock_list["symbol"]

    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    holding_detail = res.holdings_detail().reset_index(level="datetime")
    holding_detail = holding_detail[holding_detail.market_value>0]
    holding_detail["股票名称"] = stock_name
    holding_detail.reset_index(inplace=True)
    holding_detail.columns = ["股票代码","日期","股票市值","股票权重","股票名称"]
    holding_detail["日期"] = holding_detail["日期"].dt.strftime("%Y-%m-%d")
    holding_detail["股票权重"] = holding_detail['股票权重'].apply(lambda x: '{:.2%}'.format(x))
    return DFTable(holding_detail[["股票代码","股票名称","日期","股票市值","股票权重"]],name="持仓明细",mode="server-side",downloadable=True).layout

@callback(
    Output('holding_num_fig', 'figure'),
    Input('calculated_signal', 'data'),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_holding_num_fig(signal,prodcode: str, benchmark:str,date_range):
    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    return res.create_holding_num_figure()

@callback(
    Output('industry_figure_param', 'options'),
    Output('industry_figure_param', 'value'),
    Input('calculated_signal', 'data'),
    Input("industry_figure_mode", "value"),    
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_industry_param(signal,industry_figure_mode:str,prodcode: str, benchmark:str,date_range):
    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    if industry_figure_mode == "时序":
        options = [
            {"value": industry, "label": industry}
            for industry in res.industry_concentration.columns
            ]
    else:
        options = [
            {"value": date, "label": date}
            for date in res.industry_concentration.index.sort_values(ascending=False).strftime("%Y-%m-%d")
            ]
    default_value = options[0]["value"]
    return options,default_value

@callback(
    Output('industry_fig', 'figure'),
    Input("industry_figure_mode", "value"),
    Input('industry_figure_param', 'value'),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_industry_fig(industry_figure_mode:str,industry_figure_param:str,prodcode: str, benchmark:str,date_range):
    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    if industry_figure_mode == "时序":
        mode = "TS"
    else:
        mode = "CS"
    return res.create_industry_figure(mode,industry_figure_param)

@callback(
    Output('riskfactor_figure_param', 'options'),
    Output('riskfactor_figure_param', 'value'),
    Input('calculated_signal', 'data'),
    Input("riskfactor_figure_mode", "value"),    
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_riskfactor_param(signal,riskfactor_figure_mode:str,prodcode: str, benchmark:str,date_range):
    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    if riskfactor_figure_mode == "时序":
        options = [
            {"value": risk_factor, "label": risk_factor}
            for risk_factor in res.riskfactor_exposure.columns
            ]
    else:
        options = [
            {"value": risk_factor, "label": risk_factor}
            for risk_factor in res.riskfactor_exposure.index.sort_values(ascending=False).strftime("%Y-%m-%d")
            ]
    default_value = options[0]["value"]
    return options,default_value

@callback(
    Output('riskfactor_fig', 'figure'),
    Input("riskfactor_figure_mode", "value"),
    Input('riskfactor_figure_param', 'value'),
    State("perfanalysis-select-product", "value"),
    State("perfanalysis-select-benchmark", "value"),
    State("perfanalysis-date-range-picker", "value"),
    prevent_initial_call=True,
)
def update_riskfactor_fig(riskfactor_figure_mode:str,riskfactor_figure_param:str,prodcode: str, benchmark:str,date_range):
    start, end = pd.to_datetime(date_range)
    res = get_analyze_result(prodcode, benchmark,start, end)
    if riskfactor_figure_mode == "时序":
        mode = "TS"
    else:
        mode = "CS"
    return res.create_riskfactors_figure(mode,riskfactor_figure_param)